package co.edu.usbcali.raju.games.chess.engine;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import org.apache.log4j.Logger;
import jpl.Atom;
import jpl.Compound;
import jpl.Query;
import jpl.Term;
import jpl.Variable;
import co.edu.usbcali.raju.games.chess.controller.ChessController;
import co.edu.usbcali.raju.games.chess.helpers.PlBoardWrapper;
import co.edu.usbcali.raju.games.chess.model.ChessBoard;
import co.edu.usbcali.raju.games.chess.model.ChessCell;
import co.edu.usbcali.raju.games.chess.model.ChessPiece;
import co.edu.usbcali.raju.games.chess.model.ChessPosition;
import co.edu.usbcali.raju.model.Board;
import co.edu.usbcali.raju.model.Cell;
import co.edu.usbcali.raju.model.IRuleEngine;
import co.edu.usbcali.raju.model.Move;
import co.edu.usbcali.raju.model.Piece;
import co.edu.usbcali.raju.model.Step;

public class ChessRuleEngine implements IRuleEngine{

	public static final String GET_MOVE="getMove";
	public static final String TARGET_STATUS="TS";
	
	private static ChessRuleEngine instance;
	private Logger logger=Logger.getLogger(ChessRuleEngine.class.getName());
	
	public static ChessRuleEngine getInstance() {
		if(instance==null){
			instance=new ChessRuleEngine();
		}
		return instance;
	}


	@Override
	public Board transition(Board board, Move move) {
		ChessBoard chessBoard=new ChessBoard((ChessBoard)board); 
		//chessBoard.notifyMoveListeners(move);
		List<Step> steps=move.getSteps();
		for (Step step : steps) {
			int targetPosition=step.getTarget().getAbsolutePosition();
			ChessCell target=(ChessCell)chessBoard.getCells().get(targetPosition);
			if(step.getPiece()!=null){
				int sourcePosition=step.getPiece().getCell().getAbsolutePosition();
// TODO qhacer que funcione para juegos con mas de una pieza en una celda
				ChessPiece chessPiece=(ChessPiece)chessBoard.getCells().get(sourcePosition).getPieces().get(0);
			chessPiece.setCell(target);
			}else{
				target.getPieces().clear();
			}
		}
		updateStatus(chessBoard,move);
		return chessBoard;
	}
	
	public void updateStatus(Board board,Move move){
		ChessBoard cb=(ChessBoard)board;
		ChessPiece piece=(ChessPiece)move.getSteps().get(0).getPiece();
		ChessPosition position=(ChessPosition)move.getSteps().get(0).getTarget().getPosition();
		cb.getStatus().remove(piece.getPlayer().substring(0,1)+ChessBoard.ET_PASSANT);					
		if(piece!=null){
			int colS=piece.getCol();
			int rowS=piece.getRow();
			int colT=position.getCol();
			int rowT=position.getRow();
			if(piece.getType().equals(ChessPiece.ROOK)){
				if(colS==7){
					cb.getStatus().put(piece.getPlayer().substring(0,1)+ChessBoard.SHORT_CASTLING,"n");
				}else if(colS==0){
					cb.getStatus().put(piece.getPlayer().substring(0,1)+ChessBoard.LONG_CASTLING,"n");
				}
			}else if(piece.getType().equals(ChessPiece.KING)){
				cb.getStatus().put(piece.getPlayer().substring(0,1)+ChessBoard.SHORT_CASTLING,"n");
				cb.getStatus().put(piece.getPlayer().substring(0,1)+ChessBoard.LONG_CASTLING,"n");
			}else if(piece.getType().equals(ChessPiece.PAWN)){
				if(Math.abs(rowS-rowT)>1){
					cb.getStatus().put(piece.getPlayer().substring(0,1)+ChessBoard.ET_PASSANT,colT);					
				}
			}
		}
		cb.changeTurn();
	}

	
	public boolean validate(Board board,Move move){
		Term boardS=(Term)PlBoardWrapper.getInstance().exportBoard(board);
		Term statusS=(Term)PlBoardWrapper.getInstance().exportStatus(board);
		/**
		 * En los casos de enroque solo se pasa el movimiento del rey ya que prolog valida ese movimiento,
		 * no es necesario
		 */
		ChessPosition cpb=(ChessPosition)move.getSteps().get(0).getPiece().getCell().getPosition();
		ChessPosition cpe=(ChessPosition)move.getSteps().get(0).getTarget().getPosition();
		jpl.Integer xB=new jpl.Integer(cpb.getRow());
		jpl.Integer yB=new jpl.Integer(cpb.getCol());
		jpl.Integer xE=new jpl.Integer(cpe.getRow());
		jpl.Integer yE=new jpl.Integer(cpe.getCol());
		Variable statusT=new Variable(TARGET_STATUS);
		Compound askMoves=new Compound(GET_MOVE, new Term[]{boardS,statusS,xB,yB,statusT,xE,yE,new Atom(((ChessBoard)board).getTurn())});
		logger.debug("Consultando:"+Compound.toString(new Term[]{askMoves}));
		Query query=new Query(askMoves);
		//   Hashtable<String,Compound>[] st = query.allSolutions();
		query.open();
		Hashtable<String,Compound> st = query.getSolution();
		query.close();
		return (!st.isEmpty());
	}
	
	public Move buildMove(ChessBoard board,int rowS,int colS,int rowT,int colT,char c){
		logger.debug("Movimiento con promocion:"+c);
		Cell cell=board.getCell(rowT, colT);
		ChessPiece piece=(ChessPiece)board.getCell(rowS, colS).getPieces().get(0);
		ChessPiece newPiece=null;
		switch(c){
		case 'q': newPiece=new ChessPiece(ChessPiece.QUEEN,piece.getPlayer());
				break;
		case 'b': newPiece=new ChessPiece(ChessPiece.BISHOP,piece.getPlayer());
		break;
		case 'r': newPiece=new ChessPiece(ChessPiece.ROOK,piece.getPlayer());
		break;
		case 'k': newPiece=new ChessPiece(ChessPiece.KNIGHT,piece.getPlayer());
		break;
		default : logger.error("Pieza de coronación no reconocida");
		return null;
		}
		Move move=new Move(piece,cell);
		move.addStep(new Step(newPiece,cell));
		return move;
	}

	public Move buildMove(ChessBoard board,int rowS,int colS,int rowT,int colT){
		Cell cell=board.getCell(rowT, colT);
		ChessPiece piece=(ChessPiece)board.getCell(rowS, colS).getPieces().get(0);
		Move move=new Move(piece,cell);
		if(piece.getType().equals(ChessPiece.KING)){
			/**
			 * Verifica si el movimiento es un enroque o no, se valida solamente teniendo en cuenta si el rey
			 * se mueve mas de dos casillas. Se ignora si es una jugada invalida.
			 */
			if(colT-colS>1){
				Piece tower=null;
				Cell towerT=null;
				if(colT==6){ // short castling
					tower=board.getCells().get(rowS*8+7).getPieces().get(0);
					towerT=board.getCells().get(rowS*8+5);
				}else {
					if(colT==2){
						tower=board.getCells().get(rowS*8).getPieces().get(0);							
						towerT=board.getCells().get(rowS*8+3);
					}
				}
				Step castling=new Step(tower,towerT);
				move.addStep(castling);
			}
		}
		return move;
	}


	public List<Move> getMoves(ChessBoard board){
		List<Move> moves=new ArrayList<Move>();
		List<String> sMoves=PlBoardWrapper.getInstance().getMoves(board);
		for (String stringMove : sMoves) {
			Move move;
			if(stringMove.length()==5&&(stringMove.codePointAt(3)-ChessController.ASCII_0==7||stringMove.codePointAt(3)-ChessController.ASCII_0==0)){
				move=buildMove(board,stringMove.codePointAt(0)-ChessController.ASCII_0,stringMove.codePointAt(1)-ChessController.ASCII_0,
						stringMove.codePointAt(2)-ChessController.ASCII_0,stringMove.codePointAt(3)-ChessController.ASCII_0,stringMove.charAt(4));

			}else{
				move=buildMove(board,stringMove.codePointAt(0)-ChessController.ASCII_0,stringMove.codePointAt(1)-ChessController.ASCII_0,
						stringMove.codePointAt(2)-ChessController.ASCII_0,stringMove.codePointAt(3)-ChessController.ASCII_0);
			}
			moves.add(move);
		}
		return moves;
	}
	
	public boolean isTerminal(Board board){
		return PlBoardWrapper.getInstance().isTerminal((ChessBoard)board);
	}
}
